using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.Design;
using EnvDTE;
using EnvDTE80;
using Microsoft.Practices.Common;
using Microsoft.Practices.RecipeFramework;
using Microsoft.Practices.RepositoryFactory.CodeGeneration.ObjectModel;
using Property = Microsoft.Practices.RepositoryFactory.CodeGeneration.ObjectModel.Property;

namespace Microsoft.Practices.RepositoryFactory.ValueProviders
{
    /// <summary/>
    public class EntityVP : ValueProvider, IAttributesConfigurable
    {
        /// <summary/>
        public const string ProjectArgumentAttribute = "ProjectArgument";

        string projectArgument;
        Project project;

        private Dictionary<CodeClass2, int> foundClasses;

        /// <summary>
        /// Called when [before actions].
        /// </summary>
        /// <param name="currentValue">The current value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns></returns>
        public override bool OnBeforeActions(object currentValue, out object newValue)
        {
            return OnBeginRecipe(currentValue, out newValue);
        }

        /// <summary>
        /// Called when [argument changed].
        /// </summary>
        /// <param name="changedArgumentName">Name of the changed argument.</param>
        /// <param name="changedArgumentValue">The changed argument value.</param>
        /// <param name="currentValue">The current value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns></returns>
        public override bool OnArgumentChanged(string changedArgumentName, object changedArgumentValue, object currentValue, out object newValue)
        {
            return OnBeginRecipe(currentValue, out newValue);
        }

        /// <summary>
        /// Called when [begin recipe].
        /// </summary>
        /// <param name="currentValue">The current value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns></returns>
        public override bool OnBeginRecipe(object currentValue, out object newValue)
        {
            ArrayList entities = new ArrayList();
            DTE dte = GetService<DTE>(true);
            foundClasses = new Dictionary<CodeClass2, int>();

            if (!string.IsNullOrEmpty(projectArgument))
            {
                IDictionaryService dictservice = GetService<IDictionaryService>(true);
                project = dictservice.GetValue(projectArgument) as Project;

                if (project != null)
                {
                    foreach (ProjectItem projectItem in project.ProjectItems)
                    {
                        ProcessProjectItem(projectItem, entities);
                    }
                }
            }
            else
            {
                ProjectItem item = dte.SelectedItems.Item(1).ProjectItem;

                if (item != null)
                {
                    ProcessProjectItem(item, entities);
                }
            }

            newValue = entities;
            return true;
        }

        #region Private Implementation

        private void ProcessProjectItem(ProjectItem item, ArrayList entities)
        {
            if (item.FileCodeModel != null)
            {
                foreach (CodeElement element in item.FileCodeModel.CodeElements)
                {
                    ProcessCodeElement(element, entities, string.Empty);
                }
            }
            foreach (ProjectItem childItem in item.ProjectItems)
            {
                ProcessProjectItem(childItem, entities);
            }
        }

        private void ProcessCodeElement(CodeElement element, ArrayList entities, string nameSpace)
        {
            switch (element.Kind)
            {
                case vsCMElement.vsCMElementNamespace:
                    ProcessNamespaceElement((CodeNamespace)element, entities);
                    break;

                case vsCMElement.vsCMElementClass:
                    ProcessClassElement((CodeClass2)element, entities, nameSpace);
                    break;
            }
        }

        private void ProcessNamespaceElement(CodeNamespace codeNamespace, ArrayList entities)
        {
            foreach (CodeElement element in codeNamespace.Members)
            {

                string entityNamespace = string.Empty;
                switch (element.Language)
                {
                    case CodeModelLanguageConstants.vsCMLanguageVB:
                        entityNamespace = element.Name;
                        break;
                    case CodeModelLanguageConstants.vsCMLanguageCSharp:
                        entityNamespace = element.FullName;

                        entityNamespace = entityNamespace.Substring(0, entityNamespace.LastIndexOf('.'));
                        break;
                }

                ProcessCodeElement(element, entities, entityNamespace);
            }
        }

        private void ProcessClassElement(CodeClass2 codeClass2, ArrayList entities, string nameSpace)
        {
            if (!foundClasses.ContainsKey(codeClass2))
            {
                Entity entity = new Entity();
                entity.Name = codeClass2.Name;
                entity.TableName = codeClass2.Name;
                entity.Namespace = nameSpace;

                foreach (CodeElement partialClass in codeClass2.Parts)
                {
                    CodeClass2 partialCodeClass = (CodeClass2)partialClass;
                    foundClasses[partialCodeClass] = 1;
                    foreach (CodeElement child in partialCodeClass.Children)
                    {
                        ProcessClassChild(child, entity);
                    }
                }
                entities.Add(entity);
            }
        }


        private void ProcessClassChild(CodeElement child, Entity entity)
        {
            const string NULLABLE = "Nullable";

            if (child.Kind == vsCMElement.vsCMElementProperty)
            {
                CodeProperty codeProp = (CodeProperty)child;
                Property prop = new Property();
                prop.Name = codeProp.Name;
                prop.ColumnName = codeProp.Name;

                if (!string.IsNullOrEmpty(codeProp.Type.AsFullName))
                {
                    if (codeProp.Type.CodeType == null)
                    {
                        prop.Type = Type.GetType(codeProp.Type.AsFullName);
                        prop.IsNullable = false;
                    }
                    else
                    {
                        if (codeProp.Type.CodeType.Name == NULLABLE)
                        {
                            prop.Type = Type.GetType(codeProp.Type.CodeType.FullName.Replace("?", ""));
                            prop.IsNullable = true;
                        }
                        else
                        {
                            prop.Type = Type.GetType(codeProp.Type.AsFullName);
                            prop.IsNullable = false;
                        }
                    }
                }
                else
                {
                    try
                    {
                        if (codeProp.Type.ElementType != null)
                        {
                            prop.Type = Type.GetType(codeProp.Type.ElementType.AsFullName);
                        }
                    }
                    catch
                    {
                    }
                }

                if (prop.Type == null)
                {
                    if (codeProp.Type.CodeType.Kind == vsCMElement.vsCMElementEnum ||
                        codeProp.Type.CodeType.Kind == vsCMElement.vsCMElementStruct)
                        prop.Type = Type.GetType("System.ValueType");
                    else
                        prop.Type = Type.GetType("System.Object");
                }
                entity.Properties.Add(prop);
            }
        }

        #endregion

        #region IAttributesConfigurable Members

        /// <summary>
        /// Configures the component using the dictionary of attributes specified
        /// in the configuration file.
        /// </summary>
        /// <param name="attributes">The attributes in the configuration element.</param>
        public void Configure(StringDictionary attributes)
        {
            if (attributes.ContainsKey(ProjectArgumentAttribute))
            {
                projectArgument = attributes[ProjectArgumentAttribute];
            }
        }

        #endregion
    }
}
